#include "stdafx.h"
#include "MemoryManager.h"
#include "../EdgeMath/Math.h"
#include <memory>
#include <assert.h>

#define cast(ptr, type) reinterpret_cast<type*>(ptr)

using namespace EdgeUtil;

/*static*/ MemoryManager MemoryManager::instance = MemoryManager();

MemoryManager::MemoryManager(void)
{
	heap = nullptr;
	pHead = nullptr;
	heapSize = 0;
}

void MemoryManager::Init(size_t heapSize)
{
	heap = (char*)malloc(heapSize + sizeof(Node));
	pHead = (Node*)heap;
	pHead->next = nullptr;
	pHead->prev = nullptr;
	pHead->size = heapSize;
	pHead->isFree = true;
	freeList = pHead;
	this->heapSize = heapSize;
	numAllocs = 0;

	ClearMemory(pHead->getPtr(), pHead->size);
}

MemoryManager::~MemoryManager(void)
{
	free(heap);
}

void* MemoryManager::Allocate(size_t size)
{
	bool first = true;
	// find the first node that fits 'size'
	for (Node* ptr = freeList; ptr; ptr = ptr->next)
	{
		assert(ptr->isFree && "Deleted node found in free list!");
		// if the node is exactly the size we want, 
		// just return that node and mark it as unfreed
		if (ptr->size == size + sizeof(Node))
		{
			removeFromFreeList(ptr);
			tidy();
			return ptr->getPtr();
		}
		// if the node can be split, split the node and update the heap
		if (size + sizeof(Node) < ptr->size)
		{
			Node* newNode = splitNode(ptr, size);
			newNode->isFree = false;
			tidy();
			return newNode->getPtr();
		}
	}

	// if no node can be found return nullptr
	return nullptr;
}


void MemoryManager::Free(void* ptr)
{
	// find the offset into the heap that we're trying to free
	int freeOffset = int((char*)ptr - heap);
	assert(freeOffset > 0 && freeOffset < heapSize && "Delete was called on memory that was out of bounds!");
	// find the node we're trying to delete
	Node* node = cast(heap + freeOffset - sizeof(Node), Node);
	// mark this node as freed
	node->isFree = true;
	// add this node to the free list
	addToFreeList(node);
}

void MemoryManager::mergeNodes(Node* a, Node* b)
{
	a->next = b->next;
	a->size += b->size + sizeof(Node);
	if (b->next)
		b->next->prev = a;
}

MemoryManager::Node* MemoryManager::splitNode(Node* value, int splitSize)
{
	assert(value->size >= splitSize && "Ya caint split a node to a size that is larger than itself! That makes no sense!");
	Node* outval;

	outval = cast(value->getFoot() - (splitSize + sizeof(Node)), Node);
	outval->next = outval->prev = nullptr;
	outval->size = splitSize;
	value->size -= (splitSize + sizeof(Node));

	outval->isFree = true;
	ClearMemory(outval->getPtr(), outval->size);
	
	return outval;
}

void MemoryManager::ClearMemory(char* buffer, uint size)
{
	memset(buffer, 0xffffffff, size);
}

void MemoryManager::addToFreeList(Node* node)
{
	for (Node* itr = freeList; itr; itr = itr->next)
	{
		if (!itr->next)
		{
			itr->next = node;

			node->prev = itr;
			node->next = nullptr;
			node->isFree = true;

			return;
		}
	}
}

void MemoryManager::removeFromFreeList(Node* node)
{
	// if this is the first node in the free list, move the head of the list forward
	// to the next node
	if (node == freeList)
	{
		freeList = freeList->next;
		freeList->prev = nullptr;
	}
	else // update the linkage of the freeList
	{
		node->prev->next = node->next;
		node->next->prev = node->prev;
	}

	node->prev = nullptr;
	node->next = nullptr;
	node->isFree = false;
}

void MemoryManager::tidy(void)
{
	//numAllocs++;
	//if (numAllocs >= 10)
	//{
	//	for (Node* itr = freeList; itr; itr = itr->next)
	//	{
	//		// if there is not a node after this one, we are done
	//		if (!itr->next)
	//			break;

	//		// find the node after this one
	//		Node* p = (Node*)itr->getFoot();
	//		// if it's not freed, skip it
	//		if (!p->isFree)
	//			continue;

	//		// otherwise, merge the two nodes
	//		removeFromFreeList(p);
	//		itr->size += p->size;
	//		ClearMemory(itr->getPtr(), itr->size);
	//	}

	//	numAllocs = 0;
	//}
}
